package com.ktr.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;

public class DeviceUtils {

    private final List<Device> deviceList = Collections.synchronizedList(new ArrayList<>());
    private final ConcurrentHashMap<String, DeviceInfo> deviceInfoMap = new ConcurrentHashMap<>();
    private final Logger logger = LoggerFactory.getLogger(DeviceUtils.class);

    private static DeviceUtils instance;

    public static DeviceUtils getInstance() {
        if (instance == null) {
            synchronized (DeviceUtils.class) {
                if (instance == null) {
                    instance = new DeviceUtils();
                    instance.write();
                    instance.read();
                }
            }
        }
        return instance;
    }

    private DeviceUtils() {
    }

    public List<Device> getDeviceList() {
        return deviceList;
    }

    public void openSocket(String hostname, Integer port) {
        try {
            String key = hostname + "_" + port;
            if (deviceInfoMap.containsKey(key)) {
                DeviceInfo deviceInfo = deviceInfoMap.get(key);
                Selector selector = deviceInfo.getSelector();
                SocketChannel socketChannel = deviceInfo.getSocketChannel();
                if (selector.isOpen() && socketChannel.isOpen()) {
                    return;
                }
                if (selector.isOpen()) {
                    selector.close();
                }
                if (socketChannel.isOpen()) {
                    socketChannel.close();
                }
            }
            Selector selector = Selector.open();//定义一个记录套接字通道事件的对象
            SocketAddress address = new InetSocketAddress(hostname, port);//定义一个服务器地址的对象
            SocketChannel socketChannel = SocketChannel.open(address);//定义异步客户端
            socketChannel.configureBlocking(false);//将客户端设定为异步
            socketChannel.register(selector, SelectionKey.OP_READ);//在轮讯对象中注册此客户端的读取事件(就是当服务器向此客户端发送数据的时候)

            DeviceInfo deviceInfo = new DeviceInfo();
            deviceInfo.setSelector(selector);
            deviceInfo.setSocketChannel(socketChannel);
            deviceInfoMap.put(key, deviceInfo);
        } catch (IOException e) {
            this.logger.error("打开端口失败");
            this.closeSocket(hostname, port);
            return;
        }
    }

    public void closeSocket(String hostname, Integer port) {
        String key = hostname + "_" + port;
        closeSocket(key);
    }


    public void closeSocket(String key) {
        try {
            if (key == null) return;
            DeviceInfo deviceInfo = deviceInfoMap.get(key);
            if (deviceInfo != null) {
                Selector selector = deviceInfo.getSelector();
                SocketChannel socketChannel = deviceInfo.getSocketChannel();
                if (selector.isOpen()) {
                    selector.close();
                }
                if (socketChannel.isOpen()) {
                    socketChannel.close();
                }
                deviceInfoMap.remove(key);
            }

            if (deviceList.size() != 0) {
                Iterator<Device> deviceIterator = deviceList.iterator();
                while (deviceIterator.hasNext()) {
                    Device device = deviceIterator.next();
                    if (device.getKey().equals(key)) {
                        deviceIterator.remove();
                    }
                }
            }
        } catch (IOException e) {
            this.logger.error("关闭端口失败");
            return;
        }
    }

    private void write() {
        Executors.newCachedThreadPool().execute(() -> {
            while (true) {
                String key = null;
                try {
                    for (Device device : deviceList) {
                        key = device.getKey();
                        if (deviceInfoMap.containsKey(key)) {
                            DeviceInfo deviceInfo = deviceInfoMap.get(key);
                            if (deviceInfo != null && deviceInfo.getSocketChannel().isOpen()) {
                                ByteBuffer byteBuffer = ByteBuffer.allocate(32);//定义用来存储发送数据的byte缓冲区
                                byteBuffer.put(device.getInstruct());//将数据put进缓冲区
                                byteBuffer.flip();//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
                                deviceInfo.getSocketChannel().write(byteBuffer);//向服务器发送数据
                            }
                        }
                    }
                    Thread.sleep(100);
                } catch (IOException e) {
                    closeSocket(key);
                    logger.error("发送数据失败，", e.getMessage());
                } catch (InterruptedException e) {
                    logger.error("发送数据失败，", e.getMessage());
                } catch (Exception e) {
                    logger.error("发送数据失败，", e.getMessage());
                }
            }
        });
    }

    private void read() {
        Executors.newCachedThreadPool().execute(() -> {
            while (true) {
                String closeKey = null;
                try {
                    for (Map.Entry<String, DeviceInfo> entry : deviceInfoMap.entrySet()) {
                        closeKey = entry.getKey();
                        DeviceInfo deviceInfo = entry.getValue();
                        if (!deviceInfo.getSocketChannel().isOpen() || !deviceInfo.getSelector().isOpen())
                            break;//如果客户端连接没有打开就退出循环
                        int select = deviceInfo.getSelector().select();//此方法为查询是否有事件发生如果没有就阻塞,有的话返回事件数量
                        if (select == 0) {
                            Thread.sleep(200);
                            continue;
                        }//如果没有事件返回循环
                        SocketChannel sc;//定义一个临时的客户端socket对象
                        //遍例所有的事件
                        for (SelectionKey key : deviceInfo.getSelector().selectedKeys()) {
                            deviceInfo.getSelector().selectedKeys().remove(key);//删除本次事件
                            //如果本事件的类型为read时,表示服务器向本客户端发送了数据
                            if (key.isReadable()) {
                                sc = (SocketChannel) key.channel();//将临时客户端对象实例为本事件的socket对象
                                ByteArrayOutputStream bos = new ByteArrayOutputStream();//定义一个用于存储所有服务器发送过来的数据
                                ByteBuffer readBuffer = ByteBuffer.allocate(32);//定义用于接收服务器返回的数据的缓冲区
                                readBuffer.clear();//将缓冲区清空以备下次读取
                                //此循环从本事件的客户端对象读取服务器发送来的数据到缓冲区中
                                while (sc.read(readBuffer) > 0) {
                                    bos.write(readBuffer.array());//将本次读取的数据存到byte流中
                                    readBuffer.clear();//将缓冲区清空以备下次读取
                                }
                                //如果byte流中存有数据
                                if (bos.size() > 0) {
                                    byte[] b = bos.toByteArray();//建立一个普通字节数组存取缓冲区的数据
                                    for (Device device : deviceList) {
                                        if (device.getKey().equals(entry.getKey()) && device.getCode().equals(KtrUtils.getAddr(b[0]))) {
                                            Double[] data = KtrUtils.analysisData(b, device.getStart(), device.getEnd(), device.getStep());
                                            if (data != null) {
                                                for (int i = 0; i < data.length; i++) {
                                                    device.getDeviceDataMap().put(i, data[i]);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    Thread.sleep(200);
                } catch (IOException e) {
                    logger.error("读取数据失败，", e.getMessage());
                    closeSocket(closeKey);
                    break;
                } catch (InterruptedException e) {
                    this.logger.error("InterruptedException");
                    break;
                } catch (Exception e) {
                    logger.error("读取数据失败，", e.getMessage());
                }
            }
        });
    }

}

class DeviceInfo {
    private Selector selector;
    private SocketChannel socketChannel;

    public Selector getSelector() {
        return selector;
    }

    public void setSelector(Selector selector) {
        this.selector = selector;
    }

    public SocketChannel getSocketChannel() {
        return socketChannel;
    }

    public void setSocketChannel(SocketChannel socketChannel) {
        this.socketChannel = socketChannel;
    }
}